package dbopt

import (
	"database/sql"
	"errors"
	"log"
	"strings"
	"time"

	"gitee.com/yscsky/yu"
	"gorm.io/driver/mysql"
	"gorm.io/driver/postgres"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
	"gorm.io/gorm/logger"
)

// IGormOpt Gorm操作接口
type IGormOpt interface {
	Close()
	IsConflictErr(err error) bool
	CreateTable(dst any) error
	ExecSQL(sql string, values ...any) (int64, error)
	Query(dest any, stmt string, values ...any) error
	Insert(value any, conds ...clause.Expression) error
	InsertTb(tb string, value any, conds ...clause.Expression) error
	BatchInsert(value any, batchSize int, conds ...clause.Expression) error
	BatchInsertTb(tb string, value any, batchSize int, conds ...clause.Expression) error
	QueryRows(hand func(*gorm.DB, *sql.Rows) error, sql string, values ...any) error
	QueryRow(hand func(*gorm.DB, *sql.Row) error, sql string, values ...any) error
	GetGormDB() *gorm.DB
}

type gormdb struct {
	*gorm.DB
}

// MustOpen 无错的连接数据库并返回*GormDB
func MustOpen(dial gorm.Dialector, d DSN) IGormOpt {
	gdb, err := gorm.Open(
		dial,
		&gorm.Config{
			SkipDefaultTransaction: d.SkipTran,
			PrepareStmt:            d.PreStmt,
			Logger:                 logger.Default.LogMode(logger.LogLevel(d.LogLevel)),
		},
	)
	if err != nil {
		log.Fatal(err)
	}
	db, err := gdb.DB()
	if err != nil {
		log.Fatal(err)
	}
	db.SetConnMaxLifetime(time.Hour)
	db.SetMaxIdleConns(50)
	db.SetMaxOpenConns(50)
	yu.Logf("db connect %s at %s:%s", d.DBName, d.URL, d.Port)
	return &gormdb{DB: gdb}
}

// MustOpenMySQL 无错的连接MySQL返回*gorm.DB
func MustOpenMySQL(d DSN) IGormOpt {
	return MustOpen(mysql.Open(d.MySQL()), d)
}

// MustOpenPostgres 无错的连接Postgres返回*gorm.DB
func MustOpenPostgres(d DSN) IGormOpt {
	return MustOpen(postgres.Open(d.Postgres()), d)
}

// IsNoRecordErr 是否是没有记录错误
func IsNoRecordErr(err error) bool {
	return err == sql.ErrNoRows
}

// GetGormDB 获取Gorm DB
func (db *gormdb) GetGormDB() *gorm.DB {
	return db.DB
}

// CloseDB 关闭数据库连接
func (gdb *gormdb) Close() {
	if gdb.DB == nil {
		return
	}
	if db, err := gdb.DB.DB(); err == nil && db != nil {
		db.Close()
	}
}

// IsConflictErr 是否是主键冲突错误
func (db *gormdb) IsConflictErr(err error) bool {
	return strings.Contains(strings.ToLower(err.Error()), "duplicate")
}

// CreateTable 不存在的话创建表
func (gdb *gormdb) CreateTable(dst any) error {
	migrator := gdb.Migrator()
	if migrator.HasTable(dst) {
		return nil
	}
	return migrator.CreateTable(dst)
}

// ExecSQL 直接执行sql
func (db *gormdb) ExecSQL(sql string, values ...any) (int64, error) {
	tx := db.Exec(sql, values...)
	return tx.RowsAffected, tx.Error
}

// Query 查询数据
func (db *gormdb) Query(dest any, stmt string, values ...any) error {
	tx := db.Raw(stmt, values...).Scan(dest)
	if tx.Error != nil {
		return tx.Error
	}
	if tx.RowsAffected == 0 {
		return sql.ErrNoRows
	}
	return nil
}

// Insert 插入数据
func (db *gormdb) Insert(value any, conds ...clause.Expression) error {
	return db.Clauses(conds...).Create(value).Error
}

// Insert 指定表名插入数据
func (db *gormdb) InsertTb(tb string, value any, conds ...clause.Expression) error {
	if tb == "" {
		return errors.New("table name is empty")
	}
	return db.Table(tb).Clauses(conds...).Create(value).Error
}

// BatchInsert 批量插入数据
func (db *gormdb) BatchInsert(value any, batchSize int, conds ...clause.Expression) error {
	if batchSize == 0 || batchSize > 2500 {
		batchSize = 2500
	}
	return db.Clauses(conds...).CreateInBatches(value, batchSize).Error
}

// BatchInsertTb 指定表名批量插入数据
func (db *gormdb) BatchInsertTb(tb string, value any, batchSize int, conds ...clause.Expression) error {
	if tb == "" {
		return errors.New("table name is empty")
	}
	if batchSize == 0 || batchSize > 2500 {
		batchSize = 2500
	}
	return db.Table(tb).Clauses(conds...).CreateInBatches(value, batchSize).Error
}

// QueryRows 批量查询使用rows行扫描处理
func (db *gormdb) QueryRows(hand func(*gorm.DB, *sql.Rows) error, sql string, values ...any) (err error) {
	rows, err := db.Raw(sql, values...).Rows()
	if err != nil {
		return
	}
	defer rows.Close()
	for rows.Next() {
		if err = hand(db.DB, rows); err != nil {
			return
		}
	}
	return
}

// QueryRow 获取单行数据
func (db *gormdb) QueryRow(hand func(*gorm.DB, *sql.Row) error, sql string, values ...any) error {
	row := db.Raw(sql, values...).Row()
	return hand(db.DB, row)
}
